package org.jeecg.modules.payment.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.Credentials;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.payment.bo.PaymentParams;
import org.jeecg.modules.payment.bo.RefundParams;
import org.jeecg.modules.payment.bo.WechatPayChannel;
import org.jeecg.common.util.JacksonUtil;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import java.util.Random;

@Slf4j
public class WechatChannelUtil {

    private final static String paymentApi = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
    private final static String refundApi = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

    /**
     * 微信支付通道 初始化
     * @param payChannel
     * @return
     * @throws IOException
     */
    public static CloseableHttpClient setup(WechatPayChannel payChannel) throws IOException {
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(payChannel.getPrivateKeyFile()));
        //使用自动更新的签名验证器，不需要传入证书
        Credentials credentials = new WechatPay2Credentials(payChannel.getMchId(), new PrivateKeySigner(payChannel.getMchSerialNo(), merchantPrivateKey));
        byte[] bytes = payChannel.getApiV3Key().getBytes("utf-8");
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(credentials, bytes);

        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(payChannel.getMchId(), payChannel.getMchSerialNo(), merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        return httpClient;
    }

    /**
     * JSAPI下单
     * @return
     */
    public static String payment(PaymentParams params, WechatPayChannel payChannel) {
        HttpPost httpPost = new HttpPost(paymentApi);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid",payChannel.getMchId())
                .put("appid", payChannel.getAppId())
                .put("description", params.getDescription())
                .put("notify_url", params.getPaymentNotifyUrl())
                .put("out_trade_no", params.getTradeNo());
        rootNode.putObject("amount")
                .put("total", params.getPayAmount().multiply(BigDecimal.valueOf(100)).intValue());
        rootNode.putObject("payer")
                .put("openid", params.getOpenId());
        CloseableHttpClient httpClient = null;
        try {
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            httpClient = setup(payChannel); // 假设setup方法返回一个CloseableHttpClient实例
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == 200) {
                    String res = EntityUtils.toString(response.getEntity());
                    log.info("下单成功！return body = " + res);
                } else if (statusCode == 204) {
                    log.info("下单成功！statusCode == 204 " );
                } else {
                    String errMsg = "failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity());
                    log.error(errMsg);
                }
                String bodyAsString = EntityUtils.toString(response.getEntity());
                System.out.println(bodyAsString);
                return bodyAsString;
            }
            // 注意：这里不需要显式关闭response，因为try-with-resources会自动处理
        } catch (IOException e) {
            // 处理可能的IOException
            e.printStackTrace();
            throw new JeecgBootException("生成支付交易单失败");
        } finally {
            // 如果httpClient在setup方法中创建并且没有抛出异常，这里会关闭它
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // 处理httpClient关闭时的异常（尽管这通常不应该发生）
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 退款
     * @return
     */
    public static String refund(RefundParams refundParams, WechatPayChannel payChannel) {
        HttpPost httpPost = new HttpPost(refundApi);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("transaction_id",refundParams.getTransactionId())
                .put("out_trade_no", refundParams.getTradeNo())
                .put("out_refund_no", refundParams.getRefundNo())
                .put("reason", refundParams.getReason());
//                .put("notify_url", payChannel.getRefundNotifyUrl());

        rootNode.putObject("amount")
                .put("refund", refundParams.getRefundAmount().multiply(new BigDecimal(100)).intValue())
                .put("total", refundParams.getTotalAmount().multiply(new BigDecimal(100)).intValue())
                .put("currency", "CNY");
        CloseableHttpClient httpClient = null;
        try {
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            httpClient = setup(payChannel); // 假设setup方法返回一个CloseableHttpClient实例
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == 200) {
                    String res = EntityUtils.toString(response.getEntity());
                    log.info("退款成功！return body = " + res);
                }else {
                    String errMsg = "failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity());
                    log.error(errMsg);
                }
                String bodyAsString = EntityUtils.toString(response.getEntity());
                System.out.println(bodyAsString);
                return bodyAsString;
            }
        } catch (IOException e) {
            // 处理可能的IOException
            e.printStackTrace();
            throw new JeecgBootException("交易退款失败");
        } finally {
            // 如果httpClient在setup方法中创建并且没有抛出异常，这里会关闭它
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // 处理httpClient关闭时的异常（尽管这通常不应该发生）
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 生成带签名的支付信息
     * @param prePayStr
     * @return
     * @throws Exception
     */
    public static ObjectNode createSignature(String prePayStr, WechatPayChannel wechatPayChannel) {
        Map<String, String> prePayIdMap = JSON.parseObject(prePayStr, new TypeReference<Map<String, String>>() {
        });
        String timestamp = System.currentTimeMillis() / 1000 + "";
        String randomString = createNonceStr();

        StringBuilder paySign = new StringBuilder();    // 小程序签名
        paySign.append(wechatPayChannel.getAppId()).append("\n");
        paySign.append(timestamp).append("\n");
        paySign.append(randomString).append("\n");
        paySign.append("prepay_id="+prePayIdMap.get("prepay_id")).append("\n");
        String ciphertext;
        try{
            ciphertext = sign(paySign.toString().getBytes(StandardCharsets.UTF_8), wechatPayChannel.getPrivateKeyFile());
        } catch (Exception e) {
            // 处理可能的IOException
            e.printStackTrace();
            throw new JeecgBootException("生成带签名的支付信息失败");
        }

        ObjectNode rootNode = JacksonUtil.newJSONObject();
        rootNode.put("appid", wechatPayChannel.getAppId())
                .put("timestamp", timestamp)
                .put("nonce", randomString)
                .put("signType", "RSA")
                .put("paySign", ciphertext)
                .put("package", "prepay_id="+prePayIdMap.get("prepay_id"));
        return rootNode;
    }

    /**
     * 生成随机数
     * @return
     */
    public static String createNonceStr() {
        String currTime = String.format("%d", (long) System.currentTimeMillis() / 1000);
        Random random = new Random();
        int num = (int) (random.nextDouble() * (1000000 - 100000) + 100000);
        String code = String.format("%06d", num);
        return  currTime.substring(2) + code;
    }

    /**
     * 商户私钥
     * @param message
     * @return
     * @throws Exception
     */
    public static String sign(byte[] message, String privateKeyFile) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKeyFile));
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private static PrivateKey getPKCS8PrivateKey(String strPk) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] b1 = getPrivateKeyByte(strPk);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }

    private static byte[] getPrivateKeyByte(String privateKey){
        String realPK = privateKey.replaceAll("-----END PRIVATE KEY-----", "")
                .replaceAll("-----BEGIN PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        return Base64.getDecoder().decode(realPK);
    }

    public static Boolean signVerify(String serial, String message, String signature, WechatPayChannel payChannel) {
        AutoUpdateCertificatesVerifier verifier = null;
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(payChannel.getPrivateKeyFile()));
            //使用自动更新的签名验证器，不需要传入证书
             verifier = new AutoUpdateCertificatesVerifier(
                     new WechatPay2Credentials(payChannel.getMchId() ,
                             new PrivateKeySigner(payChannel.getMchSerialNo() , merchantPrivateKey)),
                     payChannel.getApiV3Key() .getBytes("utf-8"));
            return verifier.verify(serial, message.getBytes("utf-8"), signature);
        }catch (Exception e){
            throw new JeecgBootException(e.getMessage());
        }
    }

    public static String decryptSign(String body,  WechatPayChannel payChannel) {
        try {
            AesUtil util = new AesUtil(payChannel.getApiV3Key().getBytes(StandardCharsets.UTF_8));
            JSONObject jsonObj = JSON.parseObject(body);
            JSONObject resource = jsonObj.getJSONObject("resource");
            String ciphertext = resource.get("ciphertext").toString();
            String associatedData = resource.get("associated_data").toString();
            String nonce = resource.get("nonce").toString();
            return util.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        }catch (Exception e){

        }
        return null;
    }

    public static String notifyStr(boolean result) {
        String responseContent;
        if (result) {
            responseContent = "{\"code\":\"SUCCESS\",\"message\":\"success\"}";
        } else {
            responseContent = "{\"code\":\"FAIL\",\"message\":\"失败\"}";
        }
        return  responseContent;
    }
}
